package main

/**
安装依赖
  go mod init 初始化项目中的GO模块
  go get github.com/davecgh/go-spew/spew
  go get github.com/gorilla/mux
  go get github.com/joho/godotenv
*/

import (
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/davecgh/go-spew/spew"
	"github.com/gorilla/mux"
	"github.com/joho/godotenv"
)

// 定义挖矿难度
const difficult = 1

// 定义区块
type Block struct {
	Index int //记录数据
	TimeSamp string //写入数据的时间
	BPM int //每分钟跳动的次数
	Hash string //当前区块的Hash
	PreHash string //上一区块的hash
	Difficult int //当前区块的难度
	Nonce string //挖矿中符合条件的数字
}

//声明存放区块的集合
var BlockChain []Block

//使用Post传递的数据
type Message struct {
	BPM int
}

//防止同一时间产生多个区块(互斥锁)
var mutex = &sync.Mutex{}

//计算当前区块的hash
func calculateHash(block Block) string {
	//拼接区块数据
	blockData := strconv.Itoa(block.Index) + block.TimeSamp + strconv.Itoa(block.BPM) + block.PreHash + block.Nonce
	//生成hash对象
	hashObject := sha256.New()
	//输入数据
	hashObject.Write([]byte(blockData))
	//计算hash
	hash := hashObject.Sum(nil)
	//将hash值转化为16进制编码的字符串返回
	return hex.EncodeToString(hash)
}

//判断hash是否符合标准
func isHashValid(hash string, difficult int) bool {
	//根据难度定义判断条件
	prefix := strings.Repeat("0",difficult)
	//检查前缀中是否包含一样的值
	return strings.HasPrefix(hash, prefix)
}

//创建新的区块开挖矿
func generateBlock(oldBlock Block, BPM int) Block {
	var newBlock Block
	t := time.Now() //获取当前时间
	newBlock.Index = oldBlock.Index + 1
	newBlock.TimeSamp = t.String()
	newBlock.BPM = BPM
	newBlock.PreHash = oldBlock.Hash
	newBlock.Difficult = difficult
	for i := 0; ; i++ {
		hex := fmt.Sprintf("%x",i)
		newBlock.Nonce = hex
		if !isHashValid(calculateHash(newBlock),newBlock.Difficult) {
			fmt.Println(calculateHash(newBlock),"继续努力")
			//定义间隔时间
			time.Sleep(time.Second)
			continue
		}else {
			fmt.Println(calculateHash(newBlock),"牛逼，你挖矿了")
			newBlock.Hash = calculateHash(newBlock)
			break
		}
	}
	return newBlock
}

//验证区块是否合法
func isBlockValid(newBlock,oldBlock Block) bool {
	//记录数据未递增---非法
	if oldBlock.Index + 1 != newBlock.Index {
		return false
	}
	//前后hash值不相等---非法
	if oldBlock.Hash != newBlock.PreHash {
		return false
	}
	//新区块的hash值不相等---非法
	if calculateHash(newBlock) != newBlock.Hash {
		return false
	}
	return true
}

//配置web服务器
func run() error {
	mux := makeMuxRouter()
	httpAddr := os.Getenv("ADDR")
	log.Println("Listening on",os.Getenv("ADDR"))
	s := &http.Server{
		Addr:			":" + httpAddr,
		Handler:		mux,
		ReadTimeout:	10 * time.Second,
		WriteTimeout:	10 * time.Second,
		MaxHeaderBytes: 1 << 20,
	}

	if err := s.ListenAndServe(); err != nil {
		return err
	}

	return nil
}

//定义路由处理方式
func makeMuxRouter() http.Handler {
	muxRouter := mux.NewRouter()
	//收到get请求获取区块信息
	muxRouter.HandleFunc("/",handleGetBlockChain).Methods("GET")
	//收到post请求生成区块
	muxRouter.HandleFunc("/",handleWriteBlock).Methods("POST")
	return muxRouter
}

//获取所有区块的列表信息
func handleGetBlockChain(w http.ResponseWriter,r * http.Request) {
	//将区块信息转化为json格式输出
	bytes, err := json.MarshalIndent(BlockChain, ""," ")
	//如果报错将错误信息封装到http错误对象中
	if err != nil {
		http.Error(w, err.Error(),http.StatusInternalServerError)
		return 
	}
	io.WriteString(w, string(bytes))
}

func handleWriteBlock(w http.ResponseWriter, r * http.Request) {
	//设置http响应头
	w.Header().Set("Content-Type", "application/json")
	var m Message

	decoder := json.NewDecoder(r.Body)
	if err := decoder.Decode(&m); err != nil {
		respondWithJSON(w, r, http.StatusBadRequest, r.Body)
		return
	}
	defer r.Body.Close()

	//上锁
	mutex.Lock()
	newBlock := generateBlock(BlockChain[len(BlockChain) - 1],m.BPM)
	//解锁
	mutex.Unlock()
	//判断创建前的区块是否合法并打印
	if isBlockValid(newBlock, BlockChain[len(BlockChain) - 1]) {
		BlockChain = append(BlockChain, newBlock)
		//详细打印
		spew.Dump(BlockChain)
	}
}

//响应处理
func respondWithJSON(w http.ResponseWriter, r * http.Request, code int, playLoad interface{}) {
	//设置响应头
	w.Header().Set("Content-Type", "application/json")
	response, err := json.MarshalIndent(playLoad, "", " ")
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte("HTTP 500: Internal Server Error"))
		return
	}
	w.WriteHeader(code)
	w.Write(response)
}

func main(){
	//加载.env文件中的参数
	err := godotenv.Load()
	if err != nil {
		log.Fatal(err)
	}

	go func() {
		t := time.Now()
		//声明创世块
		genesisBlock := Block{}
		genesisBlock = Block{0, t.String(), 0, calculateHash(genesisBlock),"",difficult,""}
		spew.Dump(genesisBlock)

		//将创世块添加进集合中
		mutex.Lock()
		BlockChain = append(BlockChain, genesisBlock)
		mutex.Unlock()
	}()
	//启动web服务
	log.Fatal(run())
}
//由彭佳乐 赵兵 武文静 郭龙  罗赛男完成